/** * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jledit; import jline.Terminal; import jline.WindowsTerminal; import jline.console.KeyMap; import jline.console.Operation; import org.fusesource.jansi.Ansi; import org.jledit.collection.RollingStack; import org.jledit.command.Command; import org.jledit.command.CommandFactory; import org.jledit.command.undo.UndoContext; import org.jledit.command.undo.UndoContextAware; import org.jledit.command.undo.UndoableCommand; import org.jledit.jline.InputStreamReader; import org.jledit.jline.NonBlockingInputStream; import org.jledit.terminal.JlEditTerminalFactory; import org.jledit.theme.DefaultTheme; import org.jledit.theme.Theme; import org.jledit.utils.Closeables; import org.jledit.utils.JlEditConsole; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.util.LinkedList; import java.util.Stack; import static org.fusesource.jansi.Ansi.Erase; import static org.fusesource.jansi.Ansi.ansi; /** * An {@link Editor} which delegates to an other {@link Editor} implementation and displays the outcome to the console. */ public abstract class AbstractConsoleEditor implements ConsoleEditor, CommandFactory { public static final String EDITOR_NAME = "JLEdit"; public static final String DIRTY_SIGN = "*"; public static final int ESCAPE = 27; public static final int DEFAULT_ESCAPE_TIMEOUT = 100; public static final int READ_EXPIRED = -2; private final UndoContext undoContext = new UndoContext(); private final RollingStack<Coordinates> cursorPositions = new RollingStack<Coordinates>(); //The line inside the scrolling frame. //Minimum value = 1 and maximum value = terminal height - getHeaderSize() - getFooterSize(). private int frameLine = 1; private int frameColumn = 1; private final Terminal terminal; private KeyMap keys; private boolean running = false; private NonBlockingInputStream in; private long escapeTimeout; private Reader reader; private String file; private String displayAs = "<no file>"; private String title = EDITOR_NAME; private int headerSize = 1; private int footerSize = 1; private boolean readOnly = false; private boolean isOpenEnabled = true; private String highLight; private Editor<String> delegate = new StringEditor(); private Theme theme = new DefaultTheme(); public AbstractConsoleEditor(final Terminal term) throws Exception { this.terminal = JlEditTerminalFactory.get(term); } public final void init() throws Exception { this.escapeTimeout = DEFAULT_ESCAPE_TIMEOUT; boolean nonBlockingEnabled = escapeTimeout > 0L && terminal.isSupported() && in != null; /* * If we had a non-blocking thread already going, then shut it down * and start a new one. */ if (this.in != null) { this.in.shutdown(); } final InputStream wrapped = terminal.wrapInIfNeeded(System.in); this.in = new NonBlockingInputStream(wrapped, nonBlockingEnabled); this.reader = new InputStreamReader(this.in); } /** * Starts the editor. * This methods actually creates the {@link Reader}. */ public void start() { JlEditConsole.systemInstall(); running = true; try { init(); show(); while (running) { EditorOperation operation = readOperation(); if (operation != null) { Command cmd = create(operation); onCommand(cmd); } } } catch (Exception e) { //noop. } } /** * Stops the editor. * The methods clears the editor screen and also closes in/out and {@link Reader}. */ public void stop() { hide(); JlEditConsole.systemUninstall(); running = false; Closeables.closeQuitely(reader); Closeables.closeQuitely(in); } /** * Shows the editor screen. */ public void show() { repaintScreen(); frameLine = 1; frameColumn = 1; delegate.move(1, 1); flush(); } /** * Hides the editor screen and restore the {@link Terminal}. */ public void hide() { JlEditConsole.out.print("\33[" + 1 + ";" + terminal.getHeight() + ";r"); //Erase screen doesn't behave well on windows. for (int l = 1; l <= terminal.getHeight(); l++) { JlEditConsole.out.print(ansi().cursor(l, 1)); JlEditConsole.out.print(ansi().eraseLine(Erase.FORWARD)); } JlEditConsole.out.print(ansi().cursor(1, 1)); flush(); try { terminal.restore(); } catch (Exception e) { //noop } } /** * Reads a character from the user input. * * @return * @throws java.io.IOException */ @Override public int read() throws IOException { return reader.read(); } /** * Reads a boolean from the user input. * The mapping of the user input to a boolean value is specified by the implementation. * * @return */ @Override public boolean readBoolean() throws IOException { return readBoolean("", false); } /** * Displays a message and reads a boolean from the user input. * The mapping of the user input to a boolean value is specified by the implementation. * * @param message * @param defaultValue * @return */ @Override public boolean readBoolean(String message, Boolean defaultValue) throws IOException { saveCursorPosition(); Ansi style = ansi(); if (getTheme().getPromptBackground() != null) { style.bg(getTheme().getPromptBackground()); } if (getTheme().getPromptForeground() != null) { style.fg(getTheme().getPromptForeground()); } for (int i = 1; i <= getFooterSize(); i++) { JlEditConsole.out.print(ansi().cursor(terminal.getHeight() - getFooterSize() + i, 1)); JlEditConsole.out.print(style.eraseLine(Ansi.Erase.FORWARD)); } JlEditConsole.out.print(ansi().cursor(terminal.getHeight(), 1)); JlEditConsole.out.print(style.a(message).bold().eraseLine(Ansi.Erase.FORWARD)); restoreCursorPosition(); flush(); try { EditorOperation operation; while (true) { operation = readOperation(); switch (operation.getType()) { case NEWLINE: return defaultValue; case TYPE: if ("y".equals(operation.getInput()) || "Y".equals(operation.getInput())) { return true; } else if ("n".equals(operation.getInput()) || "N".equals(operation.getInput())) { return false; } } } } finally { redrawFooter(); } } /** * Reads a line from the user input. * * @return * @throws java.io.IOException */ @Override public String readLine() throws IOException { StringBuilder lineBuilder = new StringBuilder(); EditorOperation operation; while (true) { operation = readOperation(); switch (operation.getType()) { case BACKSAPCE: case DELETE: if (lineBuilder.length() > 0) { lineBuilder.delete(lineBuilder.length() - 1, lineBuilder.length()); JlEditConsole.out.print(Ansi.ansi().cursorLeft(1)); JlEditConsole.out.print(" "); JlEditConsole.out.print(Ansi.ansi().cursorLeft(1)); } break; case NEWLINE: return lineBuilder.toString(); case TYPE: JlEditConsole.out.print(operation.getInput()); lineBuilder.append(operation.getInput()); break; } flush(); } } public String readLine(String message) throws IOException { String result = null; saveCursorPosition(); Ansi style = ansi(); if (getTheme().getPromptBackground() != null) { style.bg(getTheme().getPromptBackground()); } if (getTheme().getPromptForeground() != null) { style.fg(getTheme().getPromptForeground()); } for (int i = 1; i <= getFooterSize(); i++) { JlEditConsole.out.print(ansi().cursor(terminal.getHeight() - getFooterSize() + i, 1)); JlEditConsole.out.print(style.eraseLine(Ansi.Erase.FORWARD)); } JlEditConsole.out.print(ansi().cursor(terminal.getHeight(), 1)); JlEditConsole.out.print(ansi().cursor(terminal.getHeight(), 1)); JlEditConsole.out.print(style.a(message).bold().eraseLine(Ansi.Erase.FORWARD)); flush(); try { result = readLine(); } finally { JlEditConsole.out.print(ansi().reset()); restoreCursorPosition(); redrawFooter(); } return result; } protected EditorOperation readOperation() throws IOException { StringBuilder sb = new StringBuilder(); Stack<Character> pushBackChar = new Stack<Character>(); while (true) { int c = pushBackChar.isEmpty() ? reader.read() : pushBackChar.pop(); if (c == -1) { return null; } sb.append((char) c); Object o = keys.getBound(sb); if (o == Operation.DO_LOWERCASE_VERSION) { sb.setLength(sb.length() - 1); sb.append(Character.toLowerCase((char) c)); o = keys.getBound(sb); } if (o instanceof KeyMap) { if (c == ESCAPE && pushBackChar.isEmpty() && in.isNonBlockingEnabled() && in.peek(escapeTimeout) == READ_EXPIRED) { o = ((KeyMap) o).getAnotherKey(); if (o == null || o instanceof KeyMap) { continue; } sb.setLength(0); } else { continue; } } while (o == null && sb.length() > 0) { c = sb.charAt(sb.length() - 1); sb.setLength(sb.length() - 1); Object o2 = keys.getBound(sb); if (o2 instanceof KeyMap) { o = ((KeyMap) o2).getAnotherKey(); if (o == null) { continue; } else { pushBackChar.push((char) c); } } } if (o instanceof EditorOperationType) { EditorOperationType op = (EditorOperationType) o; return new EditorOperation(op, sb.toString()); } return null; } } public void onCommand(Command command) { try { if (UndoContextAware.class.isAssignableFrom(command.getClass())) { ((UndoContextAware) command).setUndoContext(undoContext); } else if (UndoableCommand.class.isAssignableFrom(command.getClass())) { undoContext.undoPush((UndoableCommand) command); } command.execute(); if (running) { redrawCoords(); flush(); } } catch (Exception ex) { //noop. } } /** * Repaints the whole screen. */ void repaintScreen() { int repaintLine = 1; JlEditConsole.out.print(ansi().eraseScreen(Erase.ALL)); JlEditConsole.out.print(ansi().cursor(1, 1)); JlEditConsole.out.print("\33[" + (getHeaderSize() + 1) + ";" + (terminal.getHeight() - getFooterSize()) + ";r"); redrawHeader(); redrawFooter(); LinkedList<String> linesToDisplay = new LinkedList<String>(); int l = 1; while (linesToDisplay.size() < terminal.getHeight() - getFooterSize()) { String currentLine = getContent(l++); linesToDisplay.addAll(toDisplayLines(currentLine)); } for (int i = 0; i < terminal.getHeight() - getHeaderSize() - getFooterSize(); i++) { JlEditConsole.out.print(ansi().cursor(repaintLine + getHeaderSize(), 1)); displayText(linesToDisplay.get(i)); repaintLine++; } JlEditConsole.out.print(ansi().cursor(2, 1)); } /** * Redraws the rest of the multi line. */ void redrawRestOfLine() { //The number of lines to reach the end of the frame. int maxLinesToRepaint = terminal.getHeight() - getFooterSize() - frameLine; LinkedList<String> toRepaintLines = new LinkedList<String>(); String currentLine = getContent(getLine()); toRepaintLines.addAll(toDisplayLines(currentLine)); //Remove already shown lines int remainingLines = (getColumn() - 1) / terminal.getWidth(); for (int r = 0; r < remainingLines; r++) { toRepaintLines.removeFirst(); } saveCursorPosition(); for (int l = 0; l < Math.min(maxLinesToRepaint, toRepaintLines.size()); l++) { JlEditConsole.out.print(ansi().cursor(frameLine + getHeaderSize() + l, 1)); JlEditConsole.out.print(ansi().eraseLine(Erase.FORWARD)); displayText(toRepaintLines.get(l)); } restoreCursorPosition(); } /** * Redraws content from the current line to the end of the frame. */ void redrawRestOfScreen() { int linesToRepaint = terminal.getHeight() - getFooterSize() - frameLine; LinkedList<String> toRepaintLines = new LinkedList<String>(); String currentLine = getContent(getLine()); toRepaintLines.addAll(toDisplayLines(currentLine)); //Remove already shown lines int remainingLines = Math.max(0, getColumn() - 1) / terminal.getWidth(); for (int r = 0; r < remainingLines; r++) { toRepaintLines.removeFirst(); } boolean eof = false; for (int l = 1; toRepaintLines.size() < linesToRepaint && !eof; l++) { try { toRepaintLines.addAll(toDisplayLines(getContent(getLine() + l))); } catch (Exception e) { eof = true; } } saveCursorPosition(); for (int l = 0; l < linesToRepaint; l++) { JlEditConsole.out.print(ansi().cursor(frameLine + getHeaderSize() + l, 1)); JlEditConsole.out.print(ansi().eraseLine(Erase.FORWARD)); if (toRepaintLines.size() > l) { displayText(toRepaintLines.get(l)); } else { displayText(""); } } restoreCursorPosition(); } public void redrawText() { int startLine = getLine(); int startColum = getColumn(); //Go to the start of the screen (1,1) while (frameLine > 1) { moveUp(1); } while (frameColumn > 1) { moveLeft(1); } redrawRestOfScreen(); move(startLine, startColum); } @Override public int getLine() { return delegate.getLine(); } @Override public int getColumn() { return delegate.getColumn(); } @Override public void move(int line, int column) { if (line <= 0) { throw new IndexOutOfBoundsException("Minimum valid line is 1."); } int verticalOffset = line - getLine(); moveVertical(verticalOffset); int horizontalOffset = column - getColumn(); moveHorizontally(horizontalOffset); } /** * Moves the cursor vertically and scroll if needed. * * @param offset */ private void moveVertical(int offset) { if (offset < 0) { moveUp(Math.abs(offset)); } else if (offset > 0) { moveDown(offset); } } public void moveUp(int offset) { LinkedList<String> toDisplayLines = new LinkedList<String>(); for (int i = 0; i < offset; i++) { toDisplayLines.clear(); String currentLine = getContent(getLine()); toDisplayLines.addAll(toDisplayLines(currentLine)); //Remove already shown lines int remainingLines = toDisplayLines.size() - getColumn() / terminal.getWidth(); for (int r = 0; r < remainingLines; r++) { toDisplayLines.removeLast(); } delegate.move(getLine() - 1, getColumn()); currentLine = getContent(getLine()); toDisplayLines.addAll(toDisplayLines(currentLine)); for (int l = toDisplayLines.size() - 1; l >= 0; l--) { frameLine--; if (frameLine <= 0) { frameLine = 1; scrollDown(1); JlEditConsole.out.print(ansi().cursor(frameLine + getHeaderSize(), 1)); displayText(toDisplayLines.get(l)); JlEditConsole.out.print(ansi().cursor(frameLine + getHeaderSize(), getColumn())); } int actualColumn = getColumn(); while (actualColumn > terminal.getWidth()) { actualColumn -= terminal.getWidth(); } frameColumn = actualColumn; JlEditConsole.out.print(ansi().cursor(frameLine + getHeaderSize(), frameColumn)); } } } public void moveDown(int offset) { LinkedList<String> toDisplayLines = new LinkedList<String>(); for (int i = 0; i < offset; i++) { toDisplayLines.clear(); String currentLine = getContent(getLine()); toDisplayLines.addAll(toDisplayLines(currentLine)); //Remove already shown lines int remainingLines = getColumn() / terminal.getWidth() + 1; for (int r = 0; r < remainingLines; r++) { toDisplayLines.removeFirst(); } delegate.move(getLine() + 1, getColumn()); currentLine = getContent(getLine()); toDisplayLines.add(toDisplayLines(currentLine).getFirst()); for (int l = 0; l < toDisplayLines.size(); l++) { frameLine++; if (frameLine >= terminal.getHeight() - getFooterSize()) { frameLine = terminal.getHeight() - getHeaderSize() - getFooterSize(); scrollUp(1); JlEditConsole.out.print(ansi().cursor(frameLine + getHeaderSize(), 1)); displayText(toDisplayLines.get(l)); JlEditConsole.out.print(ansi().cursor(frameLine + getHeaderSize(), getColumn())); } int actualColumn = getColumn(); while (actualColumn > terminal.getWidth()) { actualColumn -= terminal.getWidth(); } frameColumn = actualColumn; JlEditConsole.out.print(ansi().cursor(frameLine + getHeaderSize(), frameColumn)); } } } /** * Moves the cursor horizontally. * * @param offset */ private void moveHorizontally(int offset) { if (offset < 0) { moveLeft(Math.abs(offset)); } else if (offset > 0) { moveRight(offset); } } public void moveLeft(int offset) { for (int i = 0; i < offset; i++) { frameColumn--; //If cursor is at the left edge of the screen. if (frameColumn <= 0) { String currentLine = getContent(getLine()); int currentLineLength = currentLine.length(); //If we are not at the first line of line projected in multiple lines: if (currentLineLength > terminal.getWidth() && getColumn() > 1) { if (frameLine != 1) { frameLine--; frameColumn = terminal.getWidth(); delegate.move(getLine(), getColumn() - 1); } else { frameLine = 1; frameColumn = 1; delegate.move(getLine(), getColumn() - 1); } } else { //Setting minimum value just in case move vertical exceeds bounds. frameColumn = 1; String previousLine = getContent(getLine() - 1); moveUp(1); //Moving up will place the cursor possibly at the start of the line. frameColumn = previousLine.length() + 1; while (frameColumn > terminal.getWidth()) { frameColumn -= terminal.getWidth(); frameLine++; } delegate.move(getLine(), previousLine.length() + 1); } } else { delegate.move(getLine(), getColumn() - 1); } } JlEditConsole.out.print(ansi().cursor(frameLine + getHeaderSize(), frameColumn)); } public void moveRight(int offset) { for (int i = 0; i < offset; i++) { int actualContentLength = getContent(getLine()).length(); frameColumn++; //Check if we need to move to the next line of the file. if (frameColumn > actualContentLength + 1 || getColumn() > actualContentLength) { frameColumn = 1; moveDown(1); moveToStartOfLine(); //Check if the current line is displayed using more lines and we need to move to the next one. } else if (frameColumn > terminal.getWidth()) { String currentLine = getContent(getLine()); frameColumn = 1; LinkedList<String> toDisplayLines = toDisplayLines(currentLine); int remainingLines = toDisplayLines.size() - getColumn() / terminal.getWidth(); for (int r = 0; r < remainingLines; r++) { toDisplayLines.removeFirst(); } frameLine++; if (frameLine >= terminal.getHeight() - getFooterSize()) { frameLine = terminal.getHeight() - getHeaderSize() - getFooterSize(); scrollUp(1); JlEditConsole.out.print(ansi().cursor(frameLine + getHeaderSize(), 1)); displayText(toDisplayLines.get(0)); JlEditConsole.out.print(ansi().cursor(frameLine + getHeaderSize(), getColumn())); } delegate.move(getLine(), getColumn() + 1); } else { //Just move to the next character. delegate.move(getLine(), getColumn() + 1); } } JlEditConsole.out.print(ansi().cursor(frameLine + getHeaderSize(), frameColumn)); } /** * Moves cursor to the end of the current line. */ public void moveToEndOfLine() { String currentLine = getContent(getLine()); LinkedList<String> toDisplayLines = toDisplayLines(currentLine); int remainingLines = getColumn() / terminal.getWidth() + 1; for (int r = 0; r < remainingLines; r++) { toDisplayLines.removeFirst(); } frameColumn = currentLine.length(); delegate.moveToEndOfLine(); for (int l = 0; l < toDisplayLines.size(); l++) { frameLine++; frameColumn -= terminal.getWidth(); if (frameLine >= terminal.getHeight() - getFooterSize()) { frameLine = terminal.getHeight() - getHeaderSize() - getFooterSize(); scrollUp(1); JlEditConsole.out.print(ansi().cursor(frameLine + getHeaderSize(), 1)); displayText(toDisplayLines.get(l)); JlEditConsole.out.print(ansi().cursor(frameLine + getHeaderSize(), getColumn())); } } JlEditConsole.out.print(ansi().cursor(frameLine + getHeaderSize(), frameColumn)); } /** * Moves cursor to the end of the current line. */ public void moveToStartOfLine() { String currentLine = getContent(getLine()); LinkedList<String> toDisplayLines = toDisplayLines(currentLine); int remainingLines = toDisplayLines.size() - getColumn() / terminal.getWidth(); for (int r = 0; r < remainingLines; r++) { toDisplayLines.removeLast(); } frameColumn = 1; delegate.moveToStartOfLine(); for (int l = toDisplayLines.size() - 1; l >= 0; l--) { frameLine--; if (frameLine <= 0) { frameLine = 1; scrollDown(1); JlEditConsole.out.print(ansi().cursor(frameLine + getHeaderSize(), 1)); displayText(toDisplayLines.get(l)); JlEditConsole.out.print(ansi().cursor(frameLine + getHeaderSize(), getColumn())); } } JlEditConsole.out.print(ansi().cursor(frameLine + getHeaderSize(), frameColumn)); } /** * Moves the cursors to the start of the line. */ @Override public void moveToStartOfFile() { delegate.moveToStartOfFile(); redrawRestOfScreen(); } /** * Moves cursor to the end of the line. */ @Override public void moveToEndOfFile() { delegate.moveToEndOfFile(); redrawText(); } @Override public void put(String str) { if (str.contains(NEW_LINE)) { String[] lines = str.split(NEW_LINE); for (int i = 0; i < lines.length; i++) { if (i != 0) { newLine(); } put(lines[i]); } if (str.endsWith(NEW_LINE)) { newLine(); } } else { int startingFromColumn = getColumn(); delegate.put(str); String modifiedLine = getContent(getLine()); LinkedList<String> toDisplayLines = toDisplayLines(modifiedLine); //We need to check if we exceed the boundaries of the line. frameColumn += str.length(); if (frameColumn > terminal.getWidth()) { int current = (startingFromColumn - 1) / terminal.getWidth(); JlEditConsole.out.print(ansi().cursor(frameLine + getHeaderSize(), 1)); JlEditConsole.out.print(ansi().eraseLine(Erase.FORWARD)); displayText(toDisplayLines.get(current)); frameLine += frameColumn / terminal.getWidth(); frameColumn -= str.length(); while (frameLine > terminal.getHeight() - getHeaderSize() - getFooterSize()) { frameLine = terminal.getHeight() - getHeaderSize() - getFooterSize(); scrollUp(1); } } if (toDisplayLines.size() > 1) { redrawRestOfScreen(); } else { redrawRestOfLine(); } frameColumn = getColumn(); while (frameColumn > terminal.getWidth()) { frameColumn -= terminal.getWidth(); } JlEditConsole.out.print(ansi().cursor(frameLine + getHeaderSize(), frameColumn)); } } @Override public String delete() { JlEditConsole.out.print(ansi().eraseLine(Erase.FORWARD)); String r = delegate.delete(); if (r.equals(NEW_LINE) || r.equals(CARRIEGE_RETURN)) { redrawRestOfScreen(); } else { redrawRestOfLine(); } return r; } @Override public String backspace() { String b = null; //Check if we need to merge with previous line. if (getColumn() == 1) { moveUp(1); moveToEndOfLine(); //end() will just move the cursor to the last char. We want to move it one more char to the right. delegate.move(getLine(), getColumn() + 1); mergeLine(); redrawRestOfScreen(); return "\n"; //Check if cursor is at the edge of a line display in multiple terminal lines. } else if (frameColumn == 1) { frameLine--; frameColumn = terminal.getWidth(); b = delegate.backspace(); String currentLine = getContent(getLine()); LinkedList<String> toDisplayLines = toDisplayLines(currentLine); int multiLineNumber = getColumn() / terminal.getWidth(); if (frameLine == 0) { frameLine = 1; scrollDown(1); } //Redraw previous line JlEditConsole.out.print(ansi().cursor(frameLine + getHeaderSize(), 1)); JlEditConsole.out.print(ansi().eraseLine(Erase.FORWARD)); displayText(toDisplayLines.get(multiLineNumber - 1)); //Redraw current line JlEditConsole.out.print(ansi().cursor(frameLine + 2, 1)); JlEditConsole.out.print(ansi().eraseLine(Erase.FORWARD)); displayText(toDisplayLines.get(multiLineNumber)); JlEditConsole.out.print(ansi().cursor(frameLine + getHeaderSize(), frameColumn)); redrawRestOfScreen(); } else { b = delegate.backspace(); String currentLine = getContent(getLine()); frameColumn--; //If we have a a simple line. if (currentLine.length() < terminal.getWidth()) { JlEditConsole.out.print(ansi().cursor(frameLine + getHeaderSize(), getColumn())); JlEditConsole.out.print(ansi().eraseLine(Erase.FORWARD)); String modifiedLine = getContent(getLine()); displayText(modifiedLine.substring(getColumn() - 1)); //Line is multi line and we will need to swift chars. } else { redrawRestOfScreen(); } JlEditConsole.out.print(ansi().cursor(frameLine + getHeaderSize(), frameColumn)); } return b; } @Override public void newLine() { delegate.newLine(); JlEditConsole.out.print(ansi().eraseLine(Erase.FORWARD)); frameColumn = 1; frameLine++; if (frameLine > terminal.getHeight() - getHeaderSize() - getFooterSize()) { frameLine = terminal.getHeight() - getHeaderSize() - getFooterSize(); scrollUp(1); } redrawRestOfScreen(); JlEditConsole.out.print(ansi().cursor(frameLine + getHeaderSize(), frameColumn)); } @Override public void mergeLine() { String currentLine = getContent(getLine()); frameColumn = currentLine.length() % terminal.getWidth() + 1; if (frameColumn == terminal.getWidth()) { frameColumn = 1; frameLine++; } delegate.mergeLine(); redrawRestOfScreen(); JlEditConsole.out.print(ansi().cursor(frameLine + getHeaderSize(), frameColumn)); } /** * Finds the next appearance of the String. * * @param str */ @Override public void findNext(String str) { int startLine = getLine(); int startColumn = getColumn(); highLight(str); delegate.findNext(str); int targetLine = getLine(); int targetColumn = getColumn(); //Rest the actual pointer delegate.move(startLine, startColumn); int verticalOffset = targetLine - getLine(); moveVertical(verticalOffset); int horizontalOffset = targetColumn - getColumn(); moveHorizontally(horizontalOffset); redrawText(); } /** * Finds the next appearance of the String. * * @param str */ @Override public void findPrevious(String str) { int startLine = getLine(); int startColumn = getColumn(); highLight(str); delegate.findPrevious(str); int targetLine = getLine(); int targetColumn = getColumn(); //Rest the actual pointer delegate.move(startLine, startColumn); int verticalOffset = targetLine - getLine(); moveVertical(verticalOffset); int horizontalOffset = targetColumn - getColumn(); moveHorizontally(horizontalOffset); redrawText(); } protected void scrollUp(int rows) { //Windows Terminals don't support scrolling. if (WindowsTerminal.class.isAssignableFrom(terminal.getClass())) { redrawText(); } else { JlEditConsole.out.print(ansi().scrollUp(rows)); } } protected void scrollDown(int rows) { //Windows Terminals don't support scrolling. if (WindowsTerminal.class.isAssignableFrom(terminal.getClass())) { redrawText(); } else { JlEditConsole.out.print(ansi().scrollDown(rows)); } } /** * Displays Text highlighting the text that was marked as highlighted. * * @param text */ protected void displayText(String text) { if (highLight != null && !highLight.isEmpty() && text.contains(highLight)) { String highLightedText = text.replaceAll(highLight, ansi().bold().bg(theme.getHighLightBackground()).fg(theme.getHighLightForeground()).a(highLight).boldOff().reset().toString()); JlEditConsole.out.print(highLightedText); } else { JlEditConsole.out.print(text); } } /** * Clears the current line. * The purpose of this method is to cover cases where erase line doesn't respect background color (e.g some Windows). */ protected void clearLine() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < terminal.getWidth(); i++) { sb.append(" "); } JlEditConsole.out.print(sb.toString()); } @Override public void saveCursorPosition() { cursorPositions.push(new Coordinates(frameLine, frameColumn)); } @Override public void restoreCursorPosition() { Coordinates coordinates = cursorPositions.pop(); if (coordinates != null) { JlEditConsole.out.print(ansi().cursor(coordinates.getLine() + getHeaderSize(), coordinates.getColumn())); } } public void flush() { JlEditConsole.out.flush(); } protected void highLight(String text) { this.highLight = text; } @Override public void open(String source, String displayAs) throws IOException { this.displayAs = displayAs; this.file = source; delegate.open(source); this.frameLine = 1; this.frameColumn = 1; } @Override public void open(String source) throws IOException { open(source, source); } @Override public void save(String target) throws IOException { if (target != null) { this.file = target; delegate.save(target); displayAs = target; } else { delegate.save(this.file); } setDirty(false); } @Override public void close() throws IOException { try { delegate.close(); file = null; } catch (Exception e) { throw new IOException(e); } } /** * Returns the number of lines. * * @return */ @Override public int lines() { return delegate.lines(); } @Override public String getContent() { return delegate.getContent(); } @Override public String getContent(int line) { return delegate.getContent(line); } public String getSource() { return file; } /** * Creates a list of lines that represent how the line will be displayed on screen. * * @param line * @return */ private LinkedList<String> toDisplayLines(String line) { LinkedList<String> displayLines = new LinkedList<String>(); if (line.length() <= terminal.getWidth()) { displayLines.add(line); } else { int total = Math.max(0, line.length() - 1) / terminal.getWidth() + 1; int startIndex = 0; for (int l = 0; l < total; l++) { displayLines.add(line.substring(startIndex, Math.min(startIndex + terminal.getWidth(), line.length()))); startIndex += terminal.getWidth(); } } return displayLines; } public Terminal getTerminal() { return terminal; } public Editor<String> getDelegate() { return delegate; } @Override public Boolean isDirty() { return delegate.isDirty(); } @Override public void setDirty(Boolean dirty) { delegate.setDirty(dirty); } /** * Sets the {@link org.jledit.ContentManager}. * @return */ @Override public ContentManager getContentManager() { return delegate.getContentManager(); } /** * Returns the {@link org.jledit.ContentManager}. * @param contentManager */ @Override public void setContentManager(ContentManager contentManager) { delegate.setContentManager(contentManager); } public int getHeaderSize() { return headerSize; } public void setHeaderSize(int headerSize) { this.headerSize = headerSize; } public int getFooterSize() { return footerSize; } public void setFooterSize(int footerSize) { this.footerSize = footerSize; } public KeyMap getKeys() { return keys; } public void setKeys(KeyMap keys) { this.keys = keys; } public Theme getTheme() { return theme; } public void setTheme(Theme theme) { this.theme = theme; } public UndoContext getUndoContext() { return undoContext; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public boolean isReadOnly() { return readOnly; } public void setReadOnly(boolean readOnly) { this.readOnly = readOnly; } public boolean isOpenEnabled() { return isOpenEnabled; } public void setOpenEnabled(boolean openEnabled) { isOpenEnabled = openEnabled; } public String getDisplayAs() { return displayAs; } public void setDisplayAs(String displayAs) { this.displayAs = displayAs; } }